package kernel;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import kernel.exception.OSException;
import kernel.process.AbstractProcess;
import kernel.process.AbstractProcessHeader;
import kernel.process.ProcessCreator;
import kernel.process.ProcessDestructor;
import kernel.process.ProcessDispatcher;

/**
 * Class responsible for checking given commands and transmitting them to ProcessCreator.
 * Runs the kernel as well.
 * @author Petr Hajek
 */
public class ProcessManager {
	private ProcessCreator pc;
	private ProcessDestructor pd;
	private long PIDs = 1;
	private static Class<?>[] classarr = {AbstractProcessHeader.class, long.class};
	private static ProcessManager instance;
	
	private ProcessManager() {
		ProcessMemory.createProcessList();
		ProcessMemory.createNewProcessQueue();
		ProcessMemory.createOldProcessQueue();
		runKernel();
	}
	
	/**
	 * Stops the running kernel.
	 */
	public void stop(){
		try {
			pc.killProcess();
			pd.killProcess();
		} catch (OSException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Returns existing instance of this class.
	 * @return
	 */
	public static ProcessManager getInstance(){
		if (instance == null){
			instance = new ProcessManager();
		}
		return instance;
	}
	
	/**
	 * Starts the kernel. 
	 */
	private void runKernel() {
		createProcessCreator();
		createProcessDestructor();
	}
	
	/**
	 * Creates Process Creator - running thread, what creates new processes.
	 */
	private void createProcessCreator() {
		pc = new ProcessCreator("kernel", null, "", "", false, 0);
		ArrayList<AbstractProcess> pcAL = new ArrayList<AbstractProcess>(0);
		pcAL.add(pc);
		ProcessDispatcher pCreator = new ProcessDispatcher(pcAL, 0);
		ProcessMemory.addProcess(pCreator);
		pCreator.setSilent(true).start();
	}
	
	/**
	 * Creates Process Creator - running thread, what deletes finished processes from process memory.
	 */
	private void createProcessDestructor() {
		pd = new ProcessDestructor("kernel", null, "", "", false, 0);
		ArrayList<AbstractProcess> pdAL = new ArrayList<AbstractProcess>(0);
		pdAL.add(pd);
		ProcessDispatcher pDestr = new ProcessDispatcher(pdAL, 0);
		ProcessMemory.addProcess(pDestr);
		pDestr.setSilent(true).start();
	}
	
	/**
	 * Checks if command is valid.
	 * @param command	Command to check.
	 * @return			
	 */
	public String existsCommand(String command) {
		for (int i = 0; i < ProcessMemory.getCommands().length; i++) {
			if (command.toLowerCase().equals(ProcessMemory.getCommands()[i][0])) {
				return ProcessMemory.getCommands()[i][1];
			}
		}
		return "";
	}
	
	/**
	 * Checks given input string (passed in the form of list of commands) and transmits them to process creator thread.
	 * @param proc 	List of parsed commands saved in structure AbstractProcessHeader.
	 */
	public void checkAndCreateProcess(List<AbstractProcessHeader> proc) {
		List<AbstractProcess> nProc = new ArrayList<AbstractProcess>(0);
		for (int i = 0; i < proc.size(); i++) {
			String com = proc.get(i).getName();
			if (!ProcessMemory.isKernelRunning() && !com.equals("kernel"))
				return;
			if (com.endsWith(":")){
				proc.get(i).setName("cd");
				List<String> param = new ArrayList<String>();
				param.add(com + File.separator);
				proc.get(i).setParams(param);
				com = "cd";
			}
			
			if (com.equals("kernel")) {
				if (ProcessMemory.isKernelRunning()) IOManager.getInstance().sendMessageToClient(proc.get(i).getShellID(), "Msg: Kernel already running!", true, null);
				else {
					IOManager.getInstance().sendMessageToClient(proc.get(i).getShellID(), "Starting kernel...", true, null);
					runKernel();
				}
				return;
			}
			
			String comClass = existsCommand(com);
			if (comClass.equals("")) {
				OSException exc = new OSException(OSException.ERR_UNKNOWN_COM);
				IOManager.getInstance().sendMessageToClient(proc.get(i).getShellID(), "Error: " + exc.getMessage(), true, null);
				return;
			}
			else {
				try {
					Class<?> cl = Class.forName(comClass);
					AbstractProcess ap = (AbstractProcess) cl.getConstructor(classarr).newInstance(proc.get(i), getNewPID());
					nProc.add(ap);
				} catch (ClassNotFoundException e) {
					OSException exc = new OSException(OSException.ERR_UNKNOWN_COM);
					IOManager.getInstance().sendMessageToClient(proc.get(i).getShellID(), "Error: " + exc.getMessage(), true, null);
					return;
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
		try {
			ProcessMemory.putIntoNewPQ(nProc);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns a generated PID for a new process.
	 * @return
	 */
	public long getNewPID() {
		return this.PIDs++;
	}

	/**
	 * Turns the operating system off.
	 */
	public void shutdown() {
		for (AbstractProcess AP: ProcessMemory.getAllPrograms()){
			try {
				AP.killProcess();
			} catch (OSException e) {
			}
		}
		
	}
}
